/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/

/*Miscellaneous architecture specific code*/



#include <shared.h>

//Backup value of the timer configuration to make bios calls
static unsigned long pm_hz = 18;

#include "irmm.h"
#define IRMM_DEST_ADDR 0x600

void setclock(unsigned long hz);
int _restore_pm()
{
	int err=0;
	_reload_gdt();
	_reload_idt();
	err=restore_irq_pm();
	setclock(pm_hz);
	return err;
}

int _prepare_rm()
{
	int err=0;
	err=prepare_irq_rm();
	if(err)
		return err;
	setclock(18); /*Set to the real mode ticking freq*/
	return err;
}


#define RM_REG_MEM 0x500

extern void callbios();

int arch_bios_int(struct regs *regs)
{
	int err=0;
	if(!regs)
		return EPINV;
	if(regs->f>0xff)
		return EPINV;
	memcpy((void *)RM_REG_MEM,regs,sizeof(struct regs));
	arch_block_interrupts();
	_prepare_rm();
	callbios(); /*asm function that jumps to 0x600 where the biosi was loaded*/
	_restore_pm();
	arch_allow_interrupts();
	memcpy(regs,(void *)RM_REG_MEM,sizeof(struct regs));
	return 0;
}



/*Intel Real Mode Management code*/
int init_irmm()
{
	struct regs regs;
	printk("Transferring irmm..\n");
	memcpy((void *)IRMM_DEST_ADDR, irmm_bin, 512); //transfer irmm
	regs.f = 0x11;
	arch_bios_int(&regs);
	return 0;
}




//Prepare a task before it is switched to. This puts the start address and the arguments to the stack and stores the new stack pointer
//Called from sched.c
void *arch_prepare_task(void *stackptr, void *fs_node, void *entry_fn)
{
	(stackptr)-=sizeof(void *);
	*((void **)(stackptr)) = fs_node;
	(stackptr)-=sizeof(void *);
	*((void **)(stackptr)) = entry_fn;
	return stackptr;
}

void end_of_task()
{
	sched_end_me();
}


/*Register memory maps specific to the architecture*/
int arch_malloc_init()
{
}

//Set the PIT
void setclock(unsigned long hz)
{
	u16 divisor = 1193182 / hz;
	outb(0x43, 0x36);
	outb(0x40, (u8)(divisor));
	outb(0x40, (u8)(divisor>>8));
}

void arch_configure_timer(int (*timer)(),unsigned long hz)
{
	setclock(hz);
	pm_hz = hz;
	arch_configure_interrupt_handler(0,timer);
}

void arch_remove_timer()
{
	arch_remove_interrupt_handler(0);
}

/*ms and us delay*/


#define NR_CALIB 5
#define CONFIG_DEFCALIB 1000
#define SYS_HZ 1000

volatile int clock_flag=1;
static int clock_handler()
{
	clock_flag=0;
	return 0;
}

u32 calibs[NR_CALIB];
u32 calib=CONFIG_DEFCALIB;

int arch_delay_ms(u32 x)
{
	u32 c,ci;
	ci=(calib * SYS_HZ) / 1000;
	while(x-- > 0) {
		c=ci;
		while(c-- > 0) {
			clock_flag=0; //Emulate the calibration environment
		}
	}
	return 0;
}

int arch_delay_us(u32 x)
{
	u32 c,ci;
	ci=(calib * SYS_HZ) / 1000000;
	while(x-- > 0) {
		c=ci;
		while(c-- > 0) {
			clock_flag=0; //Emulate the calibration environment
		}
	}
	return 0;
}


static int make_calibs()
{
	u32 i,c;
	clock_flag=1;
	//Synchronize with the clock first
	while(clock_flag);
	for(i=0;i<NR_CALIB;i++) {
		c=0;
		clock_flag=1;
		while(clock_flag) {
			c++;
		}
		calibs[i]=c;
	}
	calib=0;
	for(i=0;i<NR_CALIB;i++) {
		calib+=(calibs[i]/NR_CALIB);
	}
}

int init_delay_calibration()
{
	arch_configure_timer(clock_handler, 1000);
	make_calibs();
	make_calibs();
	printk("<%u>", calib);
	if(calib > 1000000000) {
		printk("incrementing %uG\n", calib/1000000000);
	} else if(calib > 1000000) {
		printk("incrementing %uM\n", calib/1000000);
	} else if(calib > 1000) {
		printk("incrementing %uK\n", calib/1000);
	} else {
		printk("incrementing %u\n", calib);
	}	
	arch_remove_timer();
	return 0;
}


static volatile int cnt = 0;
static int clock_handler_2()
{
	cnt++;
	return 0;
}


static inline void read_time_stamp( u32 *low, u32 *high ) {
    asm volatile( "rdtsc" : "=a" ( *low ), "=d" ( *high ) );
}

void init_cpu_speed_tsc()
{
	u32 i, j, k;
	arch_configure_timer(clock_handler_2, 1000);
	read_time_stamp(&i, &k);
	cnt = 0;
	while(cnt < 50);
	read_time_stamp(&j, &k);
	arch_remove_timer();
	k = j - i;
	k *= 20;
	if(k > 1000000000) {
		printk("CPU speed %u GHz\n", k/1000000000);
	} else if(k > 1000000) {
		printk("CPU speed %u MHz\n", k/1000000);
	} else if(k > 1000) {
		printk("CPU speed %u KHz\n", k/1000);
	} else {
		printk("CPU speed %u Hz\n", k);
	}
}

int init_arch()
{
	init_debug();
	init_irmm();
	arch_info();
	init_irq();
	init_delay_calibration();
	init_cpu_speed_tsc();
	vinit(); //ViSE startup
	return 0;
}
